const path = require('path')
const fs = require('fs')
const vm = require('vm')

function Module(id) {
    this.id = id
    this.exports = {}
}
Module.wrapper = [
    `(function(exports,require,module,__filename,__dirname){`,
    `})`
];

Module._extensions = {
    '.js'(module) {
        let content = fs.readFileSync(module.id, 'utf8')
        // 包裹一层函数
        content = Module.wrapper[0] + content +Module.wrapper[1]
        // 通过vm执行content字符串生成一个函数
        let fn = vm.runInThisContext(content)
        let exports = module.exports
        let dirname = path.dirname(module.id);
        // 执行此函数 用户会自动的给module.exports 赋值
        // 此函数执行的时候 this 发生了变化
        fn.call(exports,exports,req,module,module.id,dirname)

    },
    '.json'(module) {
        let content = fs.readFileSync(module.id, 'utf8')
        module.exports = JSON.parse(content)
    }
}
Module._resolveFilename = function (id) {
    let filePath = path.resolve(__dirname, id)
    // 判断是否是绝对路径
    if (fs.existsSync(filePath)) return filePath

    let exts = Object.keys(Module._extensions)
    for (let index = 0; index < exts.length; index++) {
        let p = filePath + exts[i]
        if (fs.existsSync(p)) return p
    }
    // 不存在尝试添加后缀，则抛错
    throw new Error('模块不存在')
}

Module.prototype.load = function () {
    // 获取扩展名
    let extName = path.extname(this.id)
    // 
    Module._extensions[extName](this)
}
// 全局的缓存区，用来缓存模块
// 如果同一路径下导入多次，则会调用缓存中的值，(性能优化点)
Module._cache = {}
function req(id) {
    // 将路径变成绝对路径，可能id后面没有后缀，需要依次添加后缀.js,.json 
    let filePath = Module._resolveFilename(id)
    // 如果缓存中有 直接使用上一次缓存的结果
    if(Module._cache[filePath]){
        return Module._cache[filePath].exports
    }
    // 生产module实例，将文件绝对路径作为module属性存入
    let module = new Module(filePath)
    // 将模块进行缓存
    Module._cache[filePath] = module  
    module.load()
    return module.exports
}

var r = req('./a.js'),
r = req('./a.js')
r = req('./a.js')
console.log(r);
setInterval(() => {
    console.log(r);
}, 1000);

// 基本类型和引用类型的区别：（涉及到按值传递）
// 1.如果导出的是一个具体的值，这个值变化了，也不会被重新渲染
// 2.如果导出的是一个引用类型，如果改变了引用类型的值，那么重新获取的就是这个更新后的内容

// commonjs导出的是值或者引用地址
// es6导出的变量


// 调试node代码
// 1.可以在chrome 浏览器中直接调试node代码  node --inspect-brk 文件名
// 2.vscode调试

// commonjs步骤
// 1.require是一个函数 接受一个./a
// 2.Module._load 拿到文件路径进行加载 ./a
// 3.内部会通过 _resolveFilename 方法 将a 文件转化成绝对路径
// 4.通过绝对路径来进行实现
// 5.内部会有判断是否是绝对路径 来推测是不是一个内置模块
// 6.创建一个模块 （id, exports）
// 7.会将模块缓存起来
// 8.真正的加载模块
// 9.通过用户的路径 获取了一个绝对路径 根据这个路径创造了一个模块  {id:文件路径， exports 导出的结果}
// 10.module.load  内部会根据后缀名 解析文件 调用对应的处理方法
// 11.最终返回的是exports 对象


// 分析实现步骤

// 1.导入相关模块，创建一个Require方法。

// 2.抽离通过Module._load方法，用于加载模块。

// 3.Module.resolveFilename 根据相对路径，转换成绝对路径。

// 4.缓存模块 Module._cache，同一个模块不要重复加载，提升性能。

// 5.创建模块 id: 保存的内容是 exports = {}相当于this。

// 6.利用tryModuleLoad(module, filename) 尝试加载模块。

// 7.Module._extensions使用读取文件。

// 8.Module.wrap: 把读取到的js包裹一个函数。

// 9.将拿到的字符串使用runInThisContext运行字符串。

// 10.让字符串执行并将this改编成exports